package com.wenx.v3auth.service;

import com.wenx.v3authserverstarter.service.RedisOAuth2AuthorizationService;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.oauth2.core.OAuth2AccessToken;
import org.springframework.security.oauth2.core.OAuth2RefreshToken;
import org.springframework.security.oauth2.core.OAuth2Token;
import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationCode;
import org.springframework.security.oauth2.server.authorization.OAuth2TokenType;
import org.springframework.stereotype.Service;

import java.time.Duration;
import java.time.Instant;
import java.util.Optional;
import java.util.Set;
import java.util.function.Supplier;
import java.util.stream.Stream;

/**
 * OAuth2令牌缓存服务
 * 优化版本，直接使用JdbcOAuth2AuthorizationService，无SQL代码
 */
@Slf4j
@Service
public class V3TokenCacheService {

    @Resource
    private RedisTemplate<String, Object> redisTemplate;
    @Resource
    private RedisOAuth2AuthorizationService authorizationService;

    // Redis键前缀常量
    private static final String AUTH_PREFIX = "oauth2:authorization:";
    private static final String ACCESS_TOKEN_PREFIX = "oauth2:token:access:";
    private static final String REFRESH_TOKEN_PREFIX = "oauth2:token:refresh:";
    private static final String CODE_PREFIX = "oauth2:token:code:";


    /**
     * 清理所有过期的Redis缓存
     */
    public void cleanupExpiredCache() {
        log.debug("开始清理过期缓存...");

        // 并行清理不同类型的缓存
        Stream.<Runnable>of(
                this::cleanupTokenMappings,
                this::cleanupAuthorizationCache
        ).parallel().forEach(Runnable::run);

        log.debug("过期缓存清理完成");
    }

    /**
     * 清理令牌映射缓存
     */
    public void cleanupTokenMappings() {
        // 使用函数式方法清理不同类型的令牌映射
        cleanupTokenMappingByType(ACCESS_TOKEN_PREFIX + "*", OAuth2TokenType.ACCESS_TOKEN, OAuth2AccessToken.class);
        cleanupTokenMappingByType(REFRESH_TOKEN_PREFIX + "*", OAuth2TokenType.REFRESH_TOKEN, OAuth2RefreshToken.class);
        cleanupTokenMappingByType(CODE_PREFIX + "*", null, OAuth2AuthorizationCode.class);
    }

    /**
     * 清理特定类型的令牌映射
     * 使用泛型和函数式编程优化代码
     */
    private <T extends OAuth2Token> void cleanupTokenMappingByType(String pattern, OAuth2TokenType tokenType, Class<T> tokenClass) {
        Set<String> keys = redisTemplate.keys(pattern);
        if (keys == null || keys.isEmpty()) {
            return;
        }

        keys.parallelStream()
                .map(key -> new TokenCacheEntry(key, redisTemplate.opsForValue().get(key)))
                .filter(entry -> entry.authId() != null)
                .forEach(entry -> {
                    OAuth2Authorization authorization = authorizationService.findById(entry.authId().toString());
                    if (!isTokenValidForType(authorization, tokenClass)) {
                        redisTemplate.delete(entry.key());
                        log.debug("已删除过期的令牌缓存: {}", entry.key());
                    }
                });
    }

    /**
     * 验证特定类型的令牌是否有效
     * 统一的令牌验证逻辑，消除重复的if-else
     */
    private <T extends OAuth2Token> boolean isTokenValidForType(OAuth2Authorization authorization, Class<T> tokenClass) {
        return Optional.ofNullable(authorization)
                .map(auth -> auth.<T>getToken(tokenClass))
                .map(OAuth2Authorization.Token::getToken)
                .map(this::isTokenNotExpired)
                .orElse(false);
    }

    /**
     * 检查令牌是否未过期
     * 统一的过期时间检查逻辑
     */
    private boolean isTokenNotExpired(OAuth2Token token) {
        if (token instanceof OAuth2AccessToken accessToken) {
            return isInstantNotExpired(accessToken.getExpiresAt());
        }
        if (token instanceof OAuth2RefreshToken refreshToken) {
            return isInstantNotExpired(refreshToken.getExpiresAt());
        }
        if (token instanceof OAuth2AuthorizationCode authCode) {
            return isInstantNotExpired(authCode.getExpiresAt());
        }
        return false;
    }

    /**
     * 检查时间点是否未过期
     */
    private boolean isInstantNotExpired(Instant expiresAt) {
        return expiresAt == null || expiresAt.isAfter(Instant.now());
    }

    /**
     * 清理授权缓存
     */
    public void cleanupAuthorizationCache() {
        Set<String> authKeys = redisTemplate.keys(AUTH_PREFIX + "*");
        if (authKeys == null || authKeys.isEmpty()) {
            return;
        }

        authKeys.parallelStream()
                .map(key -> key.substring(AUTH_PREFIX.length()))
                .forEach(authId -> {
                    OAuth2Authorization authorization = authorizationService.findById(authId);
                    if (!isAuthorizationValid(authorization)) {
                        redisTemplate.delete(AUTH_PREFIX + authId);
                        log.debug("已删除过期的授权缓存: {}", AUTH_PREFIX + authId);
                    }
                });
    }

    /**
     * 验证授权是否有效
     * 优化后的验证逻辑，检查是否有任何有效的令牌
     */
    private boolean isAuthorizationValid(OAuth2Authorization authorization) {
        return Optional.ofNullable(authorization)
                .map(auth -> Stream.<Supplier<Boolean>>of(
                        () -> isTokenValidForType(auth, OAuth2AccessToken.class),
                        () -> isTokenValidForType(auth, OAuth2RefreshToken.class),
                        () -> isTokenValidForType(auth, OAuth2AuthorizationCode.class)
                ).anyMatch(Supplier::get))
                .orElse(false);
    }

    /**
     * 刷新即将过期的访问令牌缓存
     * 移除SQL查询，使用OAuth2Service的查找能力
     */
    public void refreshExpiringTokenCache(int minutesBeforeExpiry, Duration cacheDuration) {
        try {
            // 获取所有访问令牌映射
            Set<String> accessTokenKeys = redisTemplate.keys(ACCESS_TOKEN_PREFIX + "*");
            if (accessTokenKeys == null || accessTokenKeys.isEmpty()) {
                return;
            }

            Instant expiryThreshold = Instant.now().plusSeconds(minutesBeforeExpiry * 60L);

            accessTokenKeys.parallelStream()
                    .map(this::extractTokenFromKey)
                    .filter(Optional::isPresent)
                    .map(Optional::get)
                    .forEach(tokenValue -> {
                        OAuth2Authorization auth = authorizationService.findByToken(tokenValue, OAuth2TokenType.ACCESS_TOKEN);
                        if (isTokenExpiringWithinThreshold(auth, expiryThreshold)) {
                            refreshTokenCache(tokenValue, auth.getId(), cacheDuration);
                        }
                    });

            log.debug("即将过期的令牌缓存刷新完成");

        } catch (Exception e) {
            log.error("刷新即将过期的令牌缓存时发生错误", e);
        }
    }

    /**
     * 从Redis键中提取令牌值
     */
    private Optional<String> extractTokenFromKey(String key) {
        return Optional.of(key)
                .filter(k -> k.startsWith(ACCESS_TOKEN_PREFIX))
                .map(k -> k.substring(ACCESS_TOKEN_PREFIX.length()));
    }

    /**
     * 检查令牌是否在指定阈值内即将过期
     */
    private boolean isTokenExpiringWithinThreshold(OAuth2Authorization authorization, Instant threshold) {
        return Optional.ofNullable(authorization)
                .map(auth -> auth.getToken(OAuth2AccessToken.class))
                .map(OAuth2Authorization.Token::getToken)
                .map(OAuth2AccessToken::getExpiresAt)
                .map(expiresAt -> expiresAt.isAfter(Instant.now()) && expiresAt.isBefore(threshold))
                .orElse(false);
    }

    /**
     * 刷新单个令牌的缓存
     */
    private void refreshTokenCache(String tokenValue, String authId, Duration cacheDuration) {
        String tokenKey = ACCESS_TOKEN_PREFIX + tokenValue;
        redisTemplate.opsForValue().set(tokenKey, authId, cacheDuration);
        log.debug("已刷新即将过期的令牌缓存: {}", tokenKey);
    }

    /**
     * 获取缓存统计信息
     * 优化统计逻辑，使用函数式编程
     */
    public CacheStatistics getCacheStatistics() {
        // 使用函数式方法并行获取统计
        var authKeys = getKeysCount(AUTH_PREFIX + "*");
        var accessTokenKeys = getKeysCount(ACCESS_TOKEN_PREFIX + "*");
        var refreshTokenKeys = getKeysCount(REFRESH_TOKEN_PREFIX + "*");
        var codeKeys = getKeysCount(CODE_PREFIX + "*");

        return new CacheStatistics(
                authKeys,
                accessTokenKeys + refreshTokenKeys + codeKeys,
                accessTokenKeys,
                refreshTokenKeys,
                codeKeys,
                0, // 数据库统计移除，避免SQL查询
                0  // 活跃令牌统计移除，避免SQL查询
        );
    }

    /**
     * 获取指定模式的键数量
     */
    private int getKeysCount(String pattern) {
        Set<String> keys = redisTemplate.keys(pattern);
        return keys != null ? keys.size() : 0;
    }

    /**
     * 令牌缓存条目记录
     */
    private record TokenCacheEntry(String key, Object authId) {}

    /**
     * 缓存统计信息
     */
    public record CacheStatistics(
            int authorizationCacheCount,
            int totalTokenMappings,
            int accessTokenMappings,
            int refreshTokenMappings,
            int codeMappings,
            int databaseAuthorizations,
            int activeTokens
    ) {}
}